 
/* 
 * Copyright (C) Ignacio Lucas
 */
 
/*
 *  object_pool, hecho por Ignacio Lucas
 *
 *  object_pool implementa un vector a punteros que representan objetos en un pool.
 *  Cada pool es capaz de administrar objetos del mismo tamanio.
 *  Se pueden manejar una combinacion de las siguientes funcionalidades:
 *  - un pool de tamanio fijo prealocado.
 *  - un pool incremental, pudiendo definir el tamanio de incremento y cual es la maxima cantidad de incrementos permitida.
 *
 *  Ejemplo
 *  Tamanio de objeto: 256 bytes.
 *  Pool de tamanio fijo: 1024
 *  Pool incremental: incrementos de a 256 objetos por vez, permitiendo un total de 8 incrementos.

 * Esto significa que:
 * - se tiene un total de 1024*256 bytes=256 Kbytes de memoria fija para el pool fijo.
 * - si se necesita mas memoria, se agregan hasta 8 bloques de 256 objetos cada uno. Esto significa que se puede
 *   tener hasta 2048 objetos extra en el pool, ademas de los 1024 del pool fijo.
 * 
 * Siempre que se requiere memoria, se intenta tomar del pool fijo. Si no hay objetos libres en ese pool, se busca
 * en el pool incremental. Si en ninguno de los dos casos hay un objeto libre y es posible agregar un bloque mas
 * al pool incremental, se agrega un nuevo bloque y se toma una porcion del nuevo bloque para proveer el espacio de memoria
 * solicitado.
 *
 *  Ignacio Lucas <ignacio.lucas@gmail.com>
 *
 */
#include "../common.h"
#include "object_pool.h"

#define POOL_INCREMENTAL_ENABLED 	0x01
#define POOL_FIXED_ENABLED 	0x02

// inicializacion del object_pool.
static void object_pool_setnulls(object_pool *op)
{
	op->pools_enabled = 0;
	op->conf.object_size=0;
	op->conf.incremental_length=0;
	op->conf.max_incremental_steps=0;
	op->conf.fixed_length=0;
	op->conf.exclusive=0;

	op->incremental_pool.conf = (void *)NULL;
	op->incremental_pool.bufs=(object_pool_fixed_size **)NULL;
	op->incremental_pool.used=0;
	op->incremental_pool.cache_free_buf=-1;

	op->incremental_pool.free=0;

	op->fixed_pool.conf = NULL;
	op->fixed_pool.pool=(void *)NULL;
	op->fixed_pool.indirection_pool=(unsigned char **)NULL;
	op->fixed_pool.first_item_address=(unsigned char *)NULL;
	op->fixed_pool.last_item_address=(unsigned char *)NULL;
	op->fixed_pool.free=0;

	op->id=-1;
}

int object_pool_destroy(object_pool *op)
{
	size_t i=0;
	if (op->pools_enabled & POOL_INCREMENTAL_ENABLED)
	{
		for (i=0; i<op->incremental_pool.used; i++)
		{
			free(op->incremental_pool.bufs[i]->pool);
			free(op->incremental_pool.bufs[i]->indirection_pool);
		}
		free(op->incremental_pool.bufs);
	}
	if (op->pools_enabled & POOL_FIXED_ENABLED)
	{
		free(op->fixed_pool.pool);
		free(op->fixed_pool.indirection_pool);
	}
	//object_pool_setnulls(op);
	return 0;
}

// inicializacion de un fixed_pool. Puede ser el fixed_pool global o uno de los fixed_pools participes del incremental pool.
// type=0 para el fixed_pool general. type=1 para un fixed pool participe del incremental pool.
static int object_pool_init_fixed_length(object_pool_fixed_size *fixed_pool, object_pool_conf *conf, int type)
{
	int maxlen=(type==0)?conf->fixed_length:conf->incremental_length;
	int i=0;
	
	fixed_pool->conf = conf;
	(fixed_pool->indirection_pool)=(unsigned char **)malloc(maxlen * sizeof(unsigned char*));
	if (fixed_pool->indirection_pool == (unsigned char **)NULL)
	{
		return 1;
	}
	(fixed_pool->pool)=(unsigned char *)malloc(maxlen * conf->object_size);
	if (fixed_pool->pool == (unsigned char *)NULL)
	{
		free(fixed_pool->indirection_pool);
		fixed_pool->indirection_pool=(unsigned char **)NULL;
		return 1;
	}
	// calculo la primer y ultima direccion del pool.
	fixed_pool->first_item_address = (fixed_pool->pool);
	fixed_pool->last_item_address = (fixed_pool->pool) + ((maxlen - 1) * conf->object_size);
	// Inicializo vector de indireccion.
	for (i=0; i<maxlen; i++)
	{
		fixed_pool->indirection_pool[i]=(fixed_pool->pool) + ((i) * conf->object_size);
	}
	fixed_pool->free=maxlen; // "maxlen" items libre al arrancar.
	fixed_pool->position=-1; // se inicializa con una posicion incorrecta para que sea ajustada luego.
  
	return 0;
}

int object_pool_init(object_pool *op, object_pool_conf *conf)
{
	//size_t i=0;

	object_pool_setnulls(op);

	op->conf.object_size=conf->object_size;
	op->conf.incremental_length=conf->incremental_length;
	op->conf.max_incremental_steps=conf->max_incremental_steps;
	op->conf.fixed_length=conf->fixed_length;
	op->conf.exclusive=conf->exclusive;

	op->fixed_pool.conf = &(op->conf);
	op->incremental_pool.conf = &(op->conf);
	op->conf.id=conf->id;


	// aloco memoria.
	if ((op->conf.incremental_length > 0) && (op->conf.max_incremental_steps > 0))
	{
		(op->incremental_pool.bufs)=(object_pool_fixed_size **)malloc(op->conf.max_incremental_steps * sizeof(object_pool_fixed_size *));
		if (op->incremental_pool.bufs == (object_pool_fixed_size **)NULL)
		{
			object_pool_destroy(op);
			return 1;
		}
		op->incremental_pool.used=0; // "0" incremental items usados al arrancar.
		op->incremental_pool.free=0; // "0" incremental items libres al arrancar.
		op->incremental_pool.cache_free_buf=-1;  // sin cache inicial.
		op->pools_enabled |= POOL_INCREMENTAL_ENABLED;
	}
	if (op->conf.fixed_length > 0)
	{
		if (object_pool_init_fixed_length(&(op->fixed_pool), &(op->conf), 0) != 0)
		{
			object_pool_destroy(op);
			return 1;
		}
		op->pools_enabled |= POOL_FIXED_ENABLED;
	}
	if (op->pools_enabled == 0)
	{
		return 2;
	}
	return 0;
}

// busqueda de un puntero dentro de un espacio de memoria. El puntero sera del espacio de memoria si esta comprendido entre el valor
// minimo y maximo del espacio de memoria.
static int object_pool_binary_find(const void *find_object, const void *item_on_array)
{
	object_pool_fixed_size *item=(object_pool_fixed_size *)*((object_pool_fixed_size **)item_on_array);
	unsigned char *object=(unsigned char *)find_object;

	// object esta en el fixed_pool si su valor esta entre first y last_address.
	if ((object >= item->first_item_address) && (object <= item->last_item_address))
	{
		// aprovecho para actualizar el item.
		item->indirection_pool[item->free]=object;
		item->free++;
		return 0;
	}
	if (object < item->first_item_address)
	{
		return -1;
	}
	return 1;
}

// borra el fixed_pool de la posicion "position" dentro del grupo de fixed_pools del incremental.
static int object_pool_reduce_incremental(object_pool *op, int position)
{
	int i=0;
	object_pool_fixed_size *item = (object_pool_fixed_size *)op->incremental_pool.bufs[position];

	// liberamos su memoria.
	free(item->pool);
	free(item->indirection_pool);
	free(item);

	// traigo para "atras" los items detras del de position
	for (i=position+1; i<op->incremental_pool.used; i++)
	{
		op->incremental_pool.bufs[i-1]=op->incremental_pool.bufs[i];
		// actualizo nueva posicion.
		op->incremental_pool.bufs[i-1]->position=i-1;
	}
	// actualizo cache (lo movi para atras si el cache estaba posterior al item que acabo de eliminar). 
	if (op->incremental_pool.cache_free_buf > position)
	{
		op->incremental_pool.cache_free_buf--;
	}
	else
	{
		if (op->incremental_pool.cache_free_buf == position)
		{
			// anulo el cache.
			op->incremental_pool.cache_free_buf=-1;
		}
	}

	// reduzco el total de libres segun incremental_length.
	op->incremental_pool.free-=op->conf.incremental_length;
	// un fixed_pool menos en el incremental.
	op->incremental_pool.used--;

	return 0;
}

// buscamos cual de todos los items del incremental tienen este puntero. puede ser que no lo tenga ninguno, en cuyo caso devuelvo cero. Si lo encuentro, devuelvo 1.
static int object_pool_free_on_incremental(object_pool *op, void *object)
{
	object_pool_fixed_size *found_item=NULL;
	void *void_found_item;
	int ret=0;

	if (op->incremental_pool.used > 0)
	{
		// invocacion a la busqueda binaria para saber en que fixed_pool esta un puntero.
		void_found_item=bsearch((const void *)object, (const void *)op->incremental_pool.bufs, op->incremental_pool.used, sizeof(object_pool_fixed_size *), object_pool_binary_find);
		if (void_found_item != NULL)
		{
			ret=1;

			found_item = *(object_pool_fixed_size **)void_found_item;

			// volvio un item al total de libres!
			op->incremental_pool.free++;
			if (found_item->free == op->conf.incremental_length)
			{
				// aprovechamos para reducir el incremental.
				object_pool_reduce_incremental(op, found_item->position);
			}
		}
	}
	return (ret);
}

int object_pool_put_or_free(object_pool *op, void *put_object)
{
	int freed=0;
	unsigned char *object = (unsigned char*)put_object;

	// si hay fixed_pool, verifico si el  "object" es de este pool.
	if ((op->pools_enabled & POOL_FIXED_ENABLED) && (op->fixed_pool.first_item_address <= object) && (op->fixed_pool.last_item_address >= object))
	{
		// devuelvo el object al pool de indireccion.
		op->fixed_pool.indirection_pool[op->fixed_pool.free]=object;
		op->fixed_pool.free++;
		freed=1;
	}
	else
	{
		if (op->pools_enabled & POOL_INCREMENTAL_ENABLED)
		{
			freed=object_pool_free_on_incremental(op, object);
		}
	}
	// si el elemento todavia no fue liberado, significa que esta por fuera de cualquier pool --> free a mano.
	if (freed == 0)
	{
		logger_core_error("[object_pool] LIBERO %p fuera del pool %p", object, op);

		// lo libero con void *
		free(put_object);
	}
	return 0;	
}

// agrega un fixed_pool al incremental pool de forma ordenada, para luego poder aplicar busqueda binaria.
static int object_pool_insert_ordered(object_pool *op, object_pool_fixed_size *item)
{
	// busco la posicion adecuada para insertar ordenado por el first_item_address.
	int i=0, j=0;
	char set_on_position=0;

	object_pool_fixed_size *order_item;

	for (i=0; i<op->incremental_pool.used && (set_on_position == 0); i++)
	{
		order_item = op->incremental_pool.bufs[i];
		if (order_item->first_item_address > item->first_item_address)
		{
			// movemos todos los punteros hacia el fondo.
			for (j=op->incremental_pool.used; j>i; j--)
			{
				op->incremental_pool.bufs[j]=op->incremental_pool.bufs[j-1];
				op->incremental_pool.bufs[j]->position=j;
			}
			// actualizo cache (lo movi para atras) si el valor actual de cache es >= al punto de insercion del nuevo elemento.
			if (op->incremental_pool.cache_free_buf >= i)
			{
				op->incremental_pool.cache_free_buf++;
			}

			// asigno nuevo buffer
			op->incremental_pool.bufs[i]=item;

			// asignamos posicion
			item->position=i;

			// cortamos el ciclo.
			set_on_position=1;
		}
	}
	if (set_on_position == 0)
	{
		op->incremental_pool.bufs[op->incremental_pool.used]=item;
		// asignamos posicion
		item->position=op->incremental_pool.used;
	}
	return 0;
}

// agrego un fixed_pool dentro del incremental pool.
static void *object_pool_increase_incremental_buffer(object_pool *op)
{
	void *ret=NULL;

	object_pool_fixed_size *item = (object_pool_fixed_size *)malloc(sizeof(object_pool_fixed_size));
	if (item == (object_pool_fixed_size *)NULL)
	{
		return NULL;
	}
	// alocamos memoria para los punteros.
	bzero(item, sizeof(object_pool_fixed_size));

	if (object_pool_init_fixed_length(item, &(op->conf), 1) != 0)
	{
		free(item);
		return NULL;
	}

	// actualizamos totalizadores.
	item->free=op->conf.incremental_length - 1; // resto 1 porque ya me estoy llevando un item.
	op->incremental_pool.free += item->free; // aumenta el total de items libres
	
	// ponemos el item en bufs de forma ordenada segun el first_address del nuevo item.
	object_pool_insert_ordered(op, item);

	op->incremental_pool.used++;		// aumenta el total de fixed_pools usados en el incremental.

	// quito el item.
	ret=(void *)item->indirection_pool[item->free];

	// actualizo el cache de nodo con espacio disponible. Debido a que el item recien agregado tiene espacio libre
	// (suponiendo que el incremental_length > 1), uso el mismo como mi cache_free_buf.
	op->incremental_pool.cache_free_buf=item->position;

	return ret;	
}

// buscamos un objeto libre dentro de todos los pools del incremental pool.
static void *object_pool_find_free_item_incremental(object_pool *op)
{
	int i=0;
	void *ret=NULL;
	object_pool_fixed_size *item=NULL;

	// agrego doble validacion del cache_free_buf para verificar que este en rango (cache < used).
	if ((op->incremental_pool.cache_free_buf >= 0) && (op->incremental_pool.cache_free_buf < op->incremental_pool.used) && (op->incremental_pool.bufs[op->incremental_pool.cache_free_buf]->free > 0))
	{
		item=op->incremental_pool.bufs[op->incremental_pool.cache_free_buf];
		
		// actualizamos totalizadores.
		item->free--;
		op->incremental_pool.free--;
		// quito el item.
		ret=(void *)item->indirection_pool[item->free];
	}
	if (ret == NULL)
	{
		for (i=0; i<op->incremental_pool.used; i++)
		{
			item=op->incremental_pool.bufs[i];
			if (item->free > 0)
			{
				// actualizo el cache si me encuentro con un item de mas de 1 elemento.
				if (item->free > 1)
				{
					op->incremental_pool.cache_free_buf=i;
				}

				// actualizamos totalizadores.
				item->free--;
				op->incremental_pool.free--;
				// quito el item.
				ret=(void *)item->indirection_pool[item->free];
				break;
			}
		}
	}
	return ret;
}


void * object_pool_pick_or_alloc(object_pool *op)
{
	void *ret=NULL;

	// si hay fixed_pool, verifico si hay un objeto libre en este pool.
	if ((op->pools_enabled & POOL_FIXED_ENABLED) && (op->fixed_pool.free > 0))
	{
		op->fixed_pool.free--;
		ret=(void *)op->fixed_pool.indirection_pool[op->fixed_pool.free];
	}
	else
	{
		if (op->pools_enabled & POOL_INCREMENTAL_ENABLED)
		{		
			if (op->incremental_pool.free == 0)
			{
				if (op->incremental_pool.used < op->conf.max_incremental_steps)
				{
					ret=object_pool_increase_incremental_buffer(op);
				}
			}
			else
			{
				// buscamos cual de todos los buffers usados tiene lugar.
				ret=object_pool_find_free_item_incremental(op);
			}
		}
	}

	// si no se pudo alocar un item mas, se intenta crear por fuera del pool.
	if (ret == NULL)
	{
		ret=(void *)malloc(op->conf.object_size);
		logger_core_error("[object_pool] ALOCO %p fuera del pool %p", ret, op);
	}
	return ret;
}

int object_pool_operate(object_pool *op, int (*fun_pool)(void*))
{
	return 1;
}

